package gamefw.framework;

import gamefw.gameplugin.AbstractGameLogic;
import gamefw.gameplugin.AbstractPlayer;
import gamefw.gameplugin.GameBoard;
import gamefw.gameplugin.GameEntity;
import gamefw.gameplugin.IGameAbstractFactory;
import gamefw.gameplugin.IGameResourceProperties;
import gamefw.uiplugin.IInputManager;
import gamefw.uiplugin.IOutputManager;
import gamefw.uiplugin.IUIAbstractFactory;
import gamefw.uiplugin.IUIResourceProperties;
import gamefw.uiplugin.IVisualResource;

/**
 * The Class GameManager is responsible to initialize all the components that are related.
 * In order to do that, it includes an user interface concrete factory and a game concrete factory.
 * All the initialization is done by calling the appropriate factory methods. The initialization
 * includes binding between each logical component to its corresponding physical
 * component. When the initialization is over it passes control to the
 * AbstractGameLogic which runs the gameloop.
 */
public class GameManager {
	
	/**
	 * Constructor of GameManager
	 * 
	 * @param gameFactory
	 * @param uiFactory
	 */
	public GameManager(IGameAbstractFactory gameFactory, IUIAbstractFactory uiFactory, String resourceType)
	throws Exception {
		if (gameFactory == null || uiFactory == null || resourceType == null) {
			throw new RuntimeException(new IllegalArgumentException()); 
		}
		
		// assign abstract factories.
		this.gameFactory = gameFactory;
		this.uiFactory = uiFactory;
		
		// create the resource properties classes first.
		this.gameResourceProperties = this.gameFactory.createGameResourceProperties();
		if (this.gameResourceProperties == null) {
			// TODO throw exception?
		}
		this.uiResourceProperties = this.uiFactory.createUIResourceProperties();
		if (this.uiResourceProperties == null) {
			// TODO throw exception?
		}
		
		// check if the both plugins support the specified resource type
		if (!this.gameResourceProperties.supportsResourceType(resourceType) ||
			!this.uiResourceProperties.supportsResourceType(resourceType)) {
			throw new Exception("The plugins do not support \"" + resourceType + "\" resource type.");
		}
		
		this.resourceType = resourceType;
		
		// Create game logic
		this.gameLogic = this.gameFactory.createGameLogic();
		this.gameLogic.setGameManager(this);
		
		// Initialize the UI platform
		this.uiFactory.initialize(this.gameLogic);

		// Create UI components
		this.inputManager = this.uiFactory.createInputManager();
		this.outputManager = this.uiFactory.createOutputManager();
	}
	
	/** The resource Type */
	private String resourceType;

	/** The game factory. */
	private IGameAbstractFactory gameFactory;
	
	/** The game resource properties */
	private IGameResourceProperties gameResourceProperties;
	
	/** The game logic. */
	private AbstractGameLogic gameLogic;

	
	/** The UI factory. */
	private IUIAbstractFactory uiFactory;
	
	/** The UI resource properties */
	private IUIResourceProperties uiResourceProperties;
	
	/** The input manager. */
	private IInputManager inputManager;
	
	/** The output manager. */
	private IOutputManager outputManager;
	
	/**
	 * Initializes the game logic.
	 */
	public void initGame()
	throws Exception {
		// Initialize the game logic
		this.gameLogic.initialize();
		
		// Wait until the player triggers game start.
		this.inputManager.getStartSignal();
		
		// Initialize Players
		AbstractPlayer[] players;
		try {
			players = initPlayers();
		} catch (Exception e) {
			throw new Exception("Player is null. Check if the game factory correctly creates the players");
		}
		this.gameLogic.setPlayers(players);
		
		// Initialize the game board and initial game entities
		this.gameLogic.setBoard(initBoard());
		
		// Run the game loop
		this.gameLogic.gameLoop();
		
		// TODO Print the winner and exit the game??
	}
	
	/**
	 * Initializes the players.
	 */
	private AbstractPlayer[] initPlayers() {
		// Get the number of players
		int numPlayers = this.gameLogic.getPlayersNumber();

		AbstractPlayer[] players = new AbstractPlayer[numPlayers];

		// Let the user choose the roles for each player
		for (int i = 0; i < numPlayers; ++i) {
			boolean isHuman = this.inputManager.getPlayerRole(i + 1);
			players[i] = this.gameFactory.createPlayer(isHuman);
			players[i].setIndex(i);
		}
		
		return players;
	}
	
	/**
	 * Initializes the board.
	 */
	private GameBoard initBoard() {
		GameBoard board = this.gameFactory.createGameBoard();
		
		IVisualResource visualBoard = this.uiFactory.createVisualResource(board.getVisualResource(this.resourceType));
		board.setVisualResource(visualBoard);
		
		String[] entityNames = this.gameLogic.getInitialEntities();
		GameEntity[] entities = this.gameFactory.createGameEntities(entityNames);
		
		// create all the visual entities
		if (entities != null) {
			for (GameEntity entity : entities) {
				IVisualResource visualEntity = this.uiFactory.createVisualResource(entity.getVisualResource(this.resourceType));
				entity.setVisualEntity(visualEntity);
			}
		}
		
		board.initGameBoard(entities);
		
		return board;
	}
	
	public GameEntity createEntity(String entityName) {
		GameEntity entity = this.gameFactory.createGameEntity(entityName);
		if (entity == null) {
			return null;
		}
		
		IVisualResource visualEntity = this.uiFactory.createVisualResource(entity.getVisualResource(this.resourceType));
		entity.setVisualEntity(visualEntity);
		
		return entity;
	}

	public String getResourceType() {
		return resourceType;
	}

	public IInputManager getInputManager() {
		return inputManager;
	}

	public IOutputManager getOutputManager() {
		return outputManager;
	}
	
}
